package myVPN;

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.Arrays;

import Encryption.AES_CBC;
import Encryption.Aes;
import Encryption.DH;

public class TCPClient {
	private InetAddress hostName;	
	private int portNum;
	private String secretVal;

	private String clientMessage;
	private String serverMessage;
	
	private Socket clientSocket;
	private ObjectOutputStream outToServer;
	private ObjectInputStream inFromServer;
	private int currSentIdx;
	private int currReceiveIdx;
	
	private boolean protocolSuccess;
	private String RbFromServer;	
	private int a_exp;	
	private String encrypted;
	private String decrypted;
	
	private String processedIn;
	private String processedOut;
	
	public String getEncrypted() {
		return encrypted;
	}
	public void setEncrypted(String encrypted) {
		this.encrypted = encrypted;
	}
	public String getDecrypted() {
		return decrypted;
	}
	public void setDecrypted(String decrypted) {
		this.decrypted = decrypted;
	}
	
	public String getProcessedIn() {
		return processedIn;
	}
	public void setProcessedIn(String processedIn) {
		this.processedIn = processedIn;
	}
	public String getProcessedOut() {
		return processedOut;
	}
	public void setProcessedOut(String processedOut) {
		this.processedOut = processedOut;
	}
	public int getCurrSentIdx() {
		return currSentIdx;
	}
	public void setCurrSentIdx(int currSentIdx) {
		this.currSentIdx = currSentIdx;
	}
	public int getCurrReceiveIdx() {
		return currReceiveIdx;
	}
	public void setCurrReceiveIdx(int currReceiveIdx) {
		this.currReceiveIdx = currReceiveIdx;
	}
	public String getRbFromServer() {
		return RbFromServer;
	}
	public void setRbFromServer(String rbFromServer) {
		RbFromServer = rbFromServer;
	}
	public InetAddress getHostName() {
		return hostName;
	}
	public void setHostName(String hostName) throws UnknownHostException {
		
		this.hostName = InetAddress.getByName(hostName);
	}
	public int getPortNum() {
		return portNum;
	}
	public void setPortNum(int portNum) {
		this.portNum = portNum;
	}	
	public String getSecretVal() {
		return secretVal;
	}
	public void setSecretVal(String secretVal) {
		this.secretVal = secretVal;
	}
	public String getClientMessage() {
		return clientMessage;
	}
	public void setClientMessage(String clientMessage) {
		this.clientMessage = clientMessage;
	}
	public String getServerMessage() {
		return serverMessage;
	}
	public void setServerMessage(String replyMessage) {
		this.serverMessage = replyMessage;
	}	
	public boolean isProtocolSuccess() {
		return protocolSuccess;
	}
	public void setProtocolSuccess(boolean protocolSuccess) {
		this.protocolSuccess = protocolSuccess;
	}
	
	// Constructor
	public TCPClient(){
	}
	
	public TCPClient(String name, int num, String secret) throws UnknownHostException{
		
		hostName = InetAddress.getByName(name);
		portNum = num;
		secretVal = secret;
	}
	
	// Method
	public void startConnection() throws Exception{
		clientSocket = new Socket(hostName,portNum);
	}
	
	public void doKeyExchangeOne() throws Exception{
		
		String myIP_s = "";
		String Ra = genRandomString();
		try {
			InetAddress myIP = InetAddress.getLocalHost();
			myIP_s = myIP.getHostAddress();
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		String sendStr = Ra + "/" + myIP_s;	
		
		setCurrReceiveIdx(1);
		setCurrSentIdx(1);
		clientMessage = sendStr;
		processedOut = sendStr;
		
		outToServer.writeObject(sendStr);
		outToServer.flush();
	}
	public void doKeyExchangeTwo() throws Exception{

		a_exp = genRandomInt();
		int a_public = DH.base_4.GetPublicKey(a_exp);

		String myIP_s = "";
		try {
			InetAddress myIP = InetAddress.getLocalHost();
			myIP_s = myIP.getHostAddress();
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		clientMessage = Aes.beforeEncryption(myIP_s, RbFromServer, a_public, secretVal);
		
		//byte[] encrypted = Aes.EncryptedThis(myIP_s, RbFromServer, a_public, secretVal);
		
		AES_CBC x = new AES_CBC();
		x.Encrypt(clientMessage);
		processedOut = Arrays.toString(x.EncodeEncIV());
		outToServer.writeObject(x.EncodeEncIV());
		outToServer.flush();
		
		//String sendStr = new String(encrypted);
		setCurrReceiveIdx(3);
		setCurrSentIdx(3);
		//processedOut = sendStr;
		
		//outToServer.writeObject(sendStr);
		//outToServer.flush();
	}
	
	public int getSessionKey() throws Exception{
		String plaintext = Aes.DecryptNoPadding(encrypted);
		String delims = "/";
		String[] tokens = plaintext.split(delims);
		int responseKey = Integer.parseInt(tokens[2]);
		
		return DH.GetSessionKey(a_exp, responseKey);
	}
	
	public String getSecretKey() throws Exception{
		String plaintext = Aes.DecryptNoPadding(encrypted);
		String delims = "/";
		String[] tokens = plaintext.split(delims);
		return tokens[3];
	}
	
	public void streamData() throws Exception{
		outToServer = new ObjectOutputStream(clientSocket.getOutputStream());
		outToServer.flush();
		inFromServer = new ObjectInputStream(clientSocket.getInputStream());
	}
	
	public void readMessage() throws Exception{
		if(protocolSuccess || currReceiveIdx > 1)
		{	
		  AES_CBC x = new AES_CBC();
		  byte[] inbytes = (byte[])inFromServer.readObject();
	      serverMessage =  Arrays.toString(inbytes);
	      processedIn = new String(x.DecodeAndDecrypt(inbytes, secretVal));
		}
		else
		{
			int a = currSentIdx;
			int b = currReceiveIdx;
		  byte[] inbytes = (byte[])inFromServer.readObject();
		  byte[] backup = inbytes;
		  //serverMessage =  new String(inbytes);		      
		  
		  String temp_b = new String (inbytes);
		  String delims = "/";
		  String[] tokens = temp_b.split(delims);
		  RbFromServer = tokens[0];
		  
		  byte[] rb_b = (RbFromServer + "/").getBytes();
		  int total = backup.length;
		  int rblen = rb_b.length;
		  
		  byte[] temp_e = new byte[total-rblen];
		  System.arraycopy(backup, rblen, temp_e, 0, total-rblen);
		  
		  serverMessage = RbFromServer + "/" + Arrays.toString(temp_e);
		  
		  AES_CBC x = new AES_CBC();
		  String en = new String(x.DecodeAndDecrypt(temp_e));
		  encrypted = en;
		  processedIn = RbFromServer + en;
		  
		}
	}
	
	public void sendMessage() throws Exception{
		AES_CBC x = new AES_CBC();
		byte[] encodedBytes =  x.EncryptAndEncode(clientMessage, secretVal);
		processedOut = Arrays.toString(encodedBytes);
		outToServer.writeObject(encodedBytes);
		outToServer.flush();
	}
	
	public void closeConnection() throws Exception{
		inFromServer.close();
		outToServer.close();
		clientSocket.close();
	}
	
	private int genRandomInt(){
		return (int) (Math.random() * 99);//Integer.MAX_VALUE);
	}
	private String genRandomString(){
		return Integer.toString((int) (Math.random() * 9999));//Integer.MAX_VALUE));
	}
}
